﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Threading;
using RoMBot.MemoryLib;

namespace RoMBot.Classes
{
    public class Pawn : GameObject
    {
        public int GuId = 0;
        protected byte status;
        protected PawnClass class1 = PawnClass.None;
        protected PawnClass class2 = PawnClass.None;
        public string Guild = UnknowName;
        private int level = 1;
        public int Level2 = 1;
        public int MP = 1000;
        public int MaxMP = 1000;
        public int MP2 = 1000;
        public int MaxMP2 = 1000;
        public int Race = Definitions.RACE_HUMAN;
        public int PetPtr = 0;
        public Pet Pet = null;
        private float direction = 0.0F;
        private bool alive;
        private bool mounted;

        public DateTime LastBuffUpdateTime = DateTime.Now;
        public List<Buff> Buffs = new List<Buff>();
        public List<string> Debuffs = new List<string>();

        // Experience tracking variables
        public DateTime LastExpUpdateTime = DateTime.Now;
        public int ExpUpdateInterval = 10; // Time in seconds to update exp
        public List<string> ExpTable = new List<string>(); // Holder for past exp values
        public int ExpTableMaxSize = 10; // How many values to track
        public int ExpInsertPos = 0; // Pointer to current position to overwrite (do not change)
        public int ExpPerMin = 0; // Calculated exp per minute
        public int TimeTillLevel = 0; // Time in minutes until the player will level up

        // Directed more at player, but may be changed later.
        public bool Fighting = false; // Internal use, does not depend on the client's battle flag
        public DateTime PotionLastUseTime = DateTime.Today;
        public int PotionHpUsed = 0; // counts use of HP potions
        public int PotionManaUsed = 0; // counts use of mana potions
        public DateTime PotionLastManaEmptyTime = DateTime.Today; // timer for potion empfty message
        public DateTime PotionLastHpEmptyTime = DateTime.Today; // timer for potion empfty message
        public bool Returning = false; // Whether following the return path, or regular waypoints
        public DateTime BotStartTime = DateTime.Now; // Records when the bot was started.
        public DateTime BotStartTimeNr = DateTime.Now; // Records when the bot was started, will not return at pause
        public DateTime InventoryLastUpdate = DateTime.Now; // time of the last full inventory updata
        public bool InventoryDoUpdate = false; // flag to 'force' inventory update
        public int UnstickCounter = 0; // counts unstick tries, resets if waypoint reached
        public int SuccessWaypoints = 0; // count consecutively successfull reached waypoints 
        public int LastAggroTimout = 0; // remeber last time we wait in vain for an aggro mob
        public int LevelDetectLevelup = 0; // remember player level to detect levelups
        public bool Sleeping = false; // sleep mode with fight back if attacked
        public DateTime SleepingTime; // counts the sleeping time
        public int Fights = 0; // counts the fights
        public Dictionary<string, int> mobs = new Dictionary<string, int>(); // counts the kills per target name
        public int DeathCounter = 0; // counts deaths / automatic reanimation
        public WaypointType CurrentWaypointType = WaypointType.Default; // remember current waypoint type global
        public int LastIgnoreTargetPtr = 0; // last target to ignore address
        public int LastTargetPtr = 0; // last invalid target
        public int LastIgnoreTargetTime = 0; // last target to ignore time
        public DateTime LastDistImprove = DateTime.Now; // unstick timer (dist improvement timer)
        //public DateTime lastHitTime = DateTime.MinValue;				// last time the HP of the target changed
        public bool RangedPull = false; // ranged pull phase active
        public object FreeDebug1 = null; // free field for debug use
        public object FreeField1 = null; // free field for user use
        public object FreeField2 = null; // free field for user use
        public object FreeField3 = null; // free field for user use
        public int FreeCounter1 = 0; // free counter for user use
        public int FreeCounter2 = 0; // free counter for user use
        public int FreeCounter3 = 0; // free counter for user use		
        public bool FreeFlag1 = false; // free flag for user use
        public bool FreeFlag2 = false; // free flag for user use
        public bool FreeFlag3 = false; // free flag for user use		
        //public DateTime LastSkillCastTime;			// CastTime of last skill with CastTime >0
        //public DateTime LastSkillStartTime;		// StartTime of last skill with CastTime >0
        public float LastSkillCastTime;
        public float LastSkillStartTime;
        public SkillType LastSkillType = 0; // SkillType of last skill with CastTime >0
        private BasePawnObject basePawnObject;
        private float directionY;

        internal const int LootableFlag = 0x4;
        internal const int MountedFlag = 0x10000000;

        public Pawn( int address )
            : base( address )
        { }

        public override void Update()
        {
            base.Update();

            basePawnObject = Memory.ReadObject<BasePawnObject>( this.Address ) ?? new BasePawnObject();
            this.Level2 = basePawnObject.Level2;
            this.Race = basePawnObject.Race;
            this.Speed = basePawnObject.Speed;

            this.UpdateMP();
            this.UpdateBuffs();
            this.UpdateAlive();
        }

        public float Speed { get; set; }

        private void UpdateAlive()
        {
            if ( !this.HasAddress )
            {
                this.alive = false;
                return;
            }

            var tmp = Memory.ReadRepeat<byte>( Bot.Instancia.ProcId, this.Address + Addresses.charAlive_offset );
            this.alive = !( tmp == 9 | tmp == 8 );
        }

        public virtual void UpdateMP()
        {
            if ( !this.HasAddress || !this.Exists )
            {
                this.MP = 0;
                this.MP2 = 0;
                this.MaxMP = 1;
                this.MaxMP2 = 1;
                return;
            }

            this.MP = Memory.ReadRepeat<int>( Bot.Instancia.ProcId, this.Address + Addresses.pawnMP_offset );
            this.MaxMP = Memory.ReadRepeat<int>( Bot.Instancia.ProcId, this.Address + Addresses.pawnMaxMP_offset );
            this.MP2 = Memory.ReadRepeat<int>( Bot.Instancia.ProcId, this.Address + Addresses.pawnMP2_offset );
            this.MaxMP2 = Memory.ReadRepeat<int>( Bot.Instancia.ProcId, this.Address + Addresses.pawnMaxMP2_offset );
            if ( this.MaxMP == 0 )
            {
                // Prevent division by zero for entities that have no mana
                this.MP = 1;
                this.MaxMP = 1;
            }

            if ( this.MaxMP2 == 0 )
            {
                // Prevent division by zero for entities that have no secondary mana
                this.MP2 = 1;
                this.MaxMP2 = 1;
            }

            if ( this.class1 == PawnClass.None )
            { }

            // Set the correct mana/rage/whatever
            var energyStorage1 = Definitions.ClassEnergyMap.ContainsKey( this.Class1 ) ? Definitions.ClassEnergyMap[ this.Class1 ] : "none";
            var energyStorage2 = Definitions.ClassEnergyMap.ContainsKey( this.Class2 ) ? Definitions.ClassEnergyMap[ this.Class2 ] : "none";
            if ( energyStorage1 == energyStorage2 )
            {
                energyStorage2 = "none";
            }

            switch ( energyStorage1 )
            {
                case "mana":
                    this.Mana = this.MP;
                    this.MaxMana = this.MaxMP;
                    break;
                case "rage":
                    this.Rage = this.MP;
                    this.MaxRage = this.MaxMP;
                    break;
                case "energy":
                    this.Energy = this.MP;
                    this.MaxEnergy = this.MaxMP;
                    break;
                case "focus":
                    this.Focus = this.MP;
                    this.MaxFocus = this.MaxMP;
                    break;
            }

            switch ( energyStorage2 )
            {
                case "mana":
                    this.Mana = this.MP2;
                    this.MaxMana = this.MaxMP2;
                    break;
                case "rage":
                    this.Rage = this.MP2;
                    this.MaxRage = this.MaxMP2;
                    break;
                case "energy":
                    this.Energy = this.MP2;
                    this.MaxEnergy = this.MaxMP2;
                    break;
                case "focus":
                    this.Focus = this.MP2;
                    this.MaxFocus = this.MaxMP2;
                    break;
            }
        }

        private const int BuffSize = 0x54;
        public virtual void UpdateBuffs()
        {
            if ( !this.HasAddress )
            {
                this.Buffs.Clear();
                return;
            }

            var buffStart = Memory.ReadRepeat<uint>( Bot.Instancia.ProcId, this.Address + Addresses.pawnBuffsStart_offset );
            var buffEnd = Memory.ReadRepeat<uint>( Bot.Instancia.ProcId, this.Address + Addresses.pawnBuffsEnd_offset );

            this.Buffs.Clear();

            if ( buffStart == 0 || buffEnd == 0 )
            {
                if ( Bot.DebugBuffs )
                {
                    Bot.Warning( "buffStart == 0 || buffEnd == 0" );
                }
                return;
            }
            var total = ( int ) ( buffEnd - buffStart ) / BuffSize;
            if ( total > 50 )
            { // Something wrong, too many buffs
                if ( Bot.DebugBuffs )
                {
                    Bot.Warning( "total > 50" );
                }
                return;
            }

            var arr = Memory.ReadObjectArray<BuffStruct>( ( int ) buffStart, total );
            if ( arr == null )
            {
                if ( Bot.DebugBuffs )
                {
                    Bot.Warning( "arr == null" );
                }
                return;
            }
            //for ( var i = buffStart; i < ( buffEnd - 4 ); i += ( uint ) buffSize )
            foreach ( BuffStruct buff in arr )
            {
                var name = MemoryTable.GetIdName( buff.Id );

                if ( string.IsNullOrEmpty( name ) )
                {
                    continue;
                }

                var resu = Helpers.ParseBuffName( name );
                if ( Bot.DebugBuffs && resu.Key.Contains( "Sello de Luz" ) )
                {
                    Bot.Warning( "Buff found, id: " + buff.Id + ", name: " + resu.Key + ", ( original: " + name + " ), count:" + resu.Value );
                }
                this.Buffs.Add( new Buff
                {
                    Id = buff.Id,
                    Name = resu.Key,
                    Count = resu.Value,
                    TimeLeft = buff.TimeLeft,
                    Level = buff.Level
                } );
            }
        }

        private int AsNumber( string s )
        {
            return !string.IsNullOrWhiteSpace( s ) && Char.IsNumber( s[ 0 ] ) ? Convert.ToInt32( s ) : -1;
        }

        public virtual Buff GetBuff( string buffNameOrId, int count = 0 )
        {
            if ( string.IsNullOrEmpty( buffNameOrId ) )
            {
                if ( Bot.DebugBuffs )
                {
                    Bot.Warning( "GetBuff: string.IsNullOrEmpty( buffNameOrId )" );
                }
                return null;
            }
            UpdateBuffs();
            if ( Buffs.Count <= 0 )
            {
                if ( Bot.DebugBuffs )
                {
                    Bot.Warning( "GetBuff: Buffs.Count <= 0" );
                }

                return null;
            }

            var ids = buffNameOrId.Split( new[] { ',' }, StringSplitOptions.RemoveEmptyEntries );
            var found = this.Buffs.Find( b =>
                                    {
                                        if ( null == b )
                                        {
                                            return false;
                                        }
                                        var nameFound = false;
                                        foreach ( var s in ids )
                                        {
                                            if ( b.Name.ToLowerInvariant().Equals( s.ToLowerInvariant(), StringComparison.OrdinalIgnoreCase ) )
                                            {
                                                nameFound = true;
                                                break;
                                            }
                                            if ( b.Id != AsNumber( s ) || ( count != 0 && b.Count < count ) )
                                            {
                                                if ( Bot.DebugBuffs && ( count != 0 && b.Count < count ) )
                                                {
                                                    Bot.Warning( "( count != 0 && b.Count < count ) count: " + count + " , b.Count: " + b.Count );
                                                }

                                                continue;
                                            }
                                            nameFound = true;
                                            break;
                                        }
                                        return nameFound;
                                    } );
            return found;
        }

        public virtual bool HasBuff( string buffNameOrId, int count = 0 )
        {
            return this.GetBuff( buffNameOrId, count ) != null;
        }

        public virtual void UpdateTarget()
        {
            if ( !this.HasAddress )
            {
                this.targetPtr = 0;
                this.Target = null;
                return;
            }

            var ptr = this.TargetPtr;
            if ( this.targetPtr != ptr /*|| ( _targetPtr != 0 && Target == null || ( Target.HasAddress && Target.Address != _targetPtr ) )*/ )
            {
                if ( ptr != 0 && ptr != this.Address )
                {
                    this.targetPtr = ptr;
                    this.Target = new Pawn( this.targetPtr );
                }
                else
                {
                    this.Target = null;
                }
            }
            else if ( ptr == 0 )
            {
                this.Target = null;
            }
            else if ( ptr == this.Address )
            {
                this.Target = this;
            }

            if ( this.Target != null && this.Target != this )
            {
                this.Target.Update();
            }
        }

        protected BasePawnDirection DirectionVector
        {
            get { return this.HasAddress ? Memory.ReadObject<BasePawnDirection>( this.Address ) : default( BasePawnDirection ); }
        }

        public void UpdateLevel()
        {
            if ( !this.HasAddress )
            {
                this.level = 1;
                return;
            }

            this.level = Memory.ReadRepeat<int>( Bot.Instancia.ProcId, this.Address + Addresses.pawnLevel_offset );
        }

        public bool Harvesting
        {
            get
            {
                return this.HasAddress && Memory.ReadRepeat<int>( Bot.Instancia.ProcId, this.Address + Addresses.pawnHarvesting_offset ) != 0;
            }
        }

        #region Propiedades

        public Pawn Target { get; set; }

        #endregion

        public double LastDamage
        {
            get { return this.HasAddress ? Memory.ReadRepeat<uint>( Bot.Instancia.ProcId, this.Address + Addresses.pawnLastDamage_offset ) / 1000d : 0d; }
        }

        public double MaxPower1 { get; set; }

        public double Power1 { get; set; }

        public double MaxPower2 { get; set; }

        public double Power2 { get; set; }

        public double Mana
        {
            get
            {
                if ( this.class1 == PawnClass.Mage || this.class1 == PawnClass.Knight || this.class1 == PawnClass.Priest || this.class1 == PawnClass.Druid || this.class1 == PawnClass.Warden )
                {
                    return this.Power1;
                }
                if ( this.class2 == PawnClass.Mage || this.class2 == PawnClass.Knight || this.class2 == PawnClass.Priest || this.class2 == PawnClass.Druid || this.class2 == PawnClass.Warden )
                {
                    return this.Power2;
                }

                return 0;
            }
            set
            {
                if ( this.class1 == PawnClass.Mage || this.class1 == PawnClass.Knight || this.class1 == PawnClass.Priest || this.class1 == PawnClass.Druid || this.class1 == PawnClass.Warden )
                {
                    this.Power1 = value;
                }
                else if ( this.class2 == PawnClass.Mage || this.class2 == PawnClass.Knight || this.class2 == PawnClass.Priest || this.class2 == PawnClass.Druid || this.class2 == PawnClass.Warden )
                {
                    this.Power2 = value;
                }
            }
        }

        public double MaxMana
        {
            get
            {
                if ( this.class1 == PawnClass.Mage || this.class1 == PawnClass.Knight || this.class1 == PawnClass.Priest || this.class1 == PawnClass.Druid || this.class1 == PawnClass.Warden )
                {
                    return this.MaxPower1;
                }
                if ( this.class2 == PawnClass.Mage || this.class2 == PawnClass.Knight || this.class2 == PawnClass.Priest || this.class2 == PawnClass.Druid || this.class2 == PawnClass.Warden )
                {
                    return this.MaxPower2;
                }
                return 0;
            }
            set
            {
                if ( this.class1 == PawnClass.Mage || this.class1 == PawnClass.Knight || this.class1 == PawnClass.Priest || this.class1 == PawnClass.Druid || this.class1 == PawnClass.Warden )
                {
                    this.MaxPower1 = value;
                }
                else if ( this.class2 == PawnClass.Mage || this.class2 == PawnClass.Knight || this.class2 == PawnClass.Priest || this.class2 == PawnClass.Druid || this.class2 == PawnClass.Warden )
                {
                    this.MaxPower2 = value;
                }
            }
        }

        public double Rage
        {
            get
            {
                switch ( this.class1 )
                {
                    case PawnClass.Warrior:
                        return this.Power1;
                    default:
                        if ( this.class2 == PawnClass.Warrior )
                        {
                            return this.Power2;
                        }
                        break;
                }
                return 0;
            }
            set
            {
                if ( this.class1 == PawnClass.Warrior )
                {
                    this.Power1 = value;
                }
                else if ( this.class2 == PawnClass.Warrior )
                {
                    this.Power2 = value;
                }
            }
        }

        public double MaxRage
        {
            get { return this.class1 == PawnClass.Warrior ? this.MaxPower1 : ( this.class2 == PawnClass.Warrior ? this.MaxPower2 : 0 ); }
            set
            {
                switch ( this.class1 )
                {
                    case PawnClass.Warrior:
                        this.MaxPower1 = value;
                        break;
                    default:
                        if ( this.class2 == PawnClass.Warrior )
                        {
                            this.MaxPower2 = value;
                        }
                        break;
                }
            }
        }

        public double Energy
        {
            get
            {
                if ( this.class1 == PawnClass.Rogue )
                {
                    return this.Power1;
                }
                if ( this.class2 == PawnClass.Rogue )
                {
                    return this.Power2;
                }
                return 0;
            }
            set
            {
                if ( this.class1 == PawnClass.Rogue )
                {
                    this.Power1 = value;
                }
                else if ( this.class2 == PawnClass.Rogue )
                {
                    this.Power2 = value;
                }
            }
        }

        public double MaxEnergy
        {
            get
            {
                if ( this.class1 == PawnClass.Rogue )
                {
                    return this.MaxPower1;
                }
                return this.class2 == PawnClass.Rogue ? this.MaxPower2 : 0;
            }
            set
            {
                switch ( this.class1 )
                {
                    case PawnClass.Rogue:
                        this.MaxPower1 = value;
                        break;
                    default:
                        if ( this.class2 == PawnClass.Rogue )
                        {
                            this.MaxPower2 = value;
                        }
                        break;
                }
            }
        }

        public double Focus
        {
            get
            {
                if ( this.class1 == PawnClass.Scout || this.class1 == PawnClass.Warlock )
                {
                    return this.Power1;
                }
                return ( this.class2 == PawnClass.Scout || this.class2 == PawnClass.Warlock ) ? this.Power2 : 0;
            }
            set
            {
                switch ( this.class1 )
                {
                    case PawnClass.Scout:
                    case PawnClass.Warlock:
                        this.Power1 = value;
                        break;
                    default:
                        if ( this.class2 == PawnClass.Scout || this.class2 == PawnClass.Warlock )
                        {
                            this.Power2 = value;
                        }
                        break;
                }
            }
        }

        public double MaxFocus
        {
            get
            {
                if ( this.class1 == PawnClass.Scout || this.class1 == PawnClass.Warlock )
                {
                    return this.MaxPower1;
                }
                return ( this.class2 == PawnClass.Scout || this.class2 == PawnClass.Warlock ) ? this.MaxPower2 : 0;
            }
            set
            {
                switch ( this.class1 )
                {
                    case PawnClass.Scout:
                    case PawnClass.Warlock:
                        this.MaxPower1 = value;
                        break;
                    default:
                        if ( this.class2 == PawnClass.Scout || this.class2 == PawnClass.Warlock )
                        {
                            this.MaxPower2 = value;
                        }
                        break;
                }
            }
        }

        public float DirectionY
        {
            get
            {
                var directionVector = DirectionVector;
                this.directionY = ( float ) Math.Atan2( directionVector.Y, Math.Pow( Math.Pow( directionVector.X, 2f ) + Math.Pow( directionVector.Z, 2f ), .5f ) );
                return this.directionY;
            }
            set { this.directionY = value; }
        }

        public bool Lootable
        {
            get
            {
                return this.HasAddress && Helpers.BitAnd( Memory.ReadRepeat<int>( Bot.Instancia.ProcId, this.Address + Addresses.pawnLootable_offset ), LootableFlag );
            }
        }

        internal int CastToTarget { get; set; }

        public CastingTime RemainingCastTime
        {
            get
            {
                if ( !HasAddress || !Exists )
                {
                    return new CastingTime();
                }
                var casting = Memory.ReadObject<BasePawnCasting>( this.Address );
                return new CastingTime( casting.CastTime, ( float ) ( casting.CastTime > 0 ? casting.CastTime - ( casting.CastSoFar > 0 ? casting.CastSoFar : 0 ) : 0.0 ) );
            }
        }

        public bool Alive
        {
            get
            {
                // Check if still valid target
                if ( !this.Exists )
                {
                    return false;
                }

                // Dead
                if ( this.HP < 1 )
                {
                    return false;
                }

                // Also dead (and has loot)
                if ( this.Lootable )
                {
                    return false;
                }

                this.UpdateAlive();
                return this.alive;
            }
        }

        public override int Level
        {
            get
            {
                UpdateLevel();
                return level;
            }
            set { level = value; }
        }

        public byte Status
        {
            get
            {
                this.status = Memory.ReadRepeat<byte>( Bot.Instancia.ProcId, Addresses.staticbase_char, Addresses.charBattle_offset );
                return status;
            }
        }

        public virtual bool Battling
        {
            get
            {
                return Status == 1;
            }
        }

        public bool Mounted
        {
            get
            {
                if ( !this.HasAddress )
                {
                    this.mounted = false;
                }
                else
                {
                    var aFlag = this.AttackableFlag;//Single read
                    this.mounted = aFlag != 0 && Helpers.BitAnd( aFlag, MountedFlag );
                }
                return mounted;
            }
            set { mounted = value; }
        }

        public PawnClass Class1
        {
            get
            {
                this.class1 = !this.HasAddress ? PawnClass.None : ( PawnClass ) Memory.ReadRepeat<int>( Bot.Instancia.ProcId, this.Address + Addresses.pawnClass1_offset );
                return class1;
            }
            set { class1 = value; }
        }

        public PawnClass Class2
        {
            get
            {
                this.class2 = !this.HasAddress ? PawnClass.None : ( PawnClass ) Memory.ReadRepeat<int>( Bot.Instancia.ProcId, this.Address + Addresses.pawnClass2_offset );

                return class2;
            }
            set { class2 = value; }
        }

        public bool Casting
        {
            get
            {
                return this.HasAddress && ( Memory.ReadRepeatPtr<int>( Addresses.castingBarPtr, Addresses.castingBar_offset ) != 0 );
            }
        }

        public float Direction
        {
            get
            {
                var directionVector = DirectionVector;
                direction = ( float ) Math.Atan2( directionVector.Z, directionVector.X );
                return direction;
            }
            set
            {
                direction = value;
            }
        }

        /// <summary>
        /// Check if this is on player mobs ignore list
        /// </summary>
        /// <returns>true if mob is on player ignored mobs list, false otherwise.</returns>
        public bool IsOnMobIgnoreList()
        {
            var esta = false;
            var ignored = Bot.Player.MobIgnoreList.FirstOrDefault( v => v.Address == this.Address );
            if ( ignored != null )
            {
                if ( Helpers.Distance( Bot.Player, Bot.Player.LastPlaceMobIgnored ) < 50 ||
                     ( DateTime.Now - ignored.Time ).Seconds < 10 )
                {
                    esta = true;
                }
                else
                {
                    //Lo quitamos de ignorados para que vuelva a intentar
                    Bot.Player.MobIgnoreList.Remove( ignored );
                }
            }
            return esta;
        }
    }
}